package core.buffer;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class Buffer {

    private byte[] _buffer;
    private int _readIndex;
    private int _writeIndex;
    private int _startIndex;
    private int _endIndex;
    private boolean _reverse;

    public Buffer(int capacity) {
        byte[] buffer = new byte[capacity];
        reset(buffer, 0, buffer.length, false);
    }

    private Buffer(int capacity, boolean reverse) {
        byte[] buffer = new byte[capacity];
        reset(buffer, 0, buffer.length, reverse);
    }

    public Buffer(byte[] buffer, int offset, int length) {
        reset(buffer, offset, length, false);
    }

    public void reset(byte[] buffer, int offset, int length, boolean reverse) {
        _buffer = buffer;
        _startIndex = offset;
        _endIndex = offset + length;
        _reverse = reverse;
        clear();
    }

    public boolean dirty() {
        return _writeIndex != (_reverse ? _endIndex : _startIndex);
    }

    public int readRemaining() {
        int remaining = _reverse ? _readIndex - _writeIndex : _writeIndex - _readIndex;
        return Math.max(remaining, 0);
    }

    public int writeRemaining() {
        int remaining = _reverse ? _writeIndex - _startIndex : _endIndex - _writeIndex;
        return Math.max(remaining, 0);
    }

    public static int fill(Buffer src, Buffer dist) {
        int copyCount = Math.min(src.readRemaining(), dist.writeRemaining());
        System.arraycopy(src._buffer, src._readIndex, dist._buffer, dist._writeIndex, copyCount);
        src._readIndex += copyCount;
        dist._writeIndex += copyCount;
        return copyCount;
    }

    public int write(InputStream in) throws IOException {
        if (writeRemaining() <= 0) {
            return 0;
        }
        int readCount = in.read(_buffer, _writeIndex, writeRemaining());
        _writeIndex += readCount;
        return readCount;
    }

    public int read(OutputStream out) throws IOException {
        if (readRemaining() <= 0) {
            return 0;
        }
        int readCount = readRemaining();
        out.write(_buffer, _readIndex, readCount);
        _readIndex += readCount;
        return readCount;
    }

    public byte read() {
        if (readRemaining() < 1) throw new IndexOutOfBoundsException();
        return _buffer[_readIndex++];
    }

    public void write(byte value) {
        if (writeRemaining() < 1) throw new IndexOutOfBoundsException();
        _buffer[_writeIndex++] = value;
    }

    public boolean setValue(int index, byte value) {
        int setIndex = index + _startIndex;
        if (setIndex < _readIndex || setIndex > _writeIndex) return false;
        _buffer[setIndex] = value;
        return true;
    }

    public boolean writeJump(int count) {
        int value = _writeIndex + count;
        if (value < _startIndex || value > _writeIndex) return false;
        _writeIndex += count;
        return true;
    }

    public Buffer writeInt(int value) {
        if (writeRemaining() < 4) throw new IndexOutOfBoundsException();
        _buffer[_writeIndex++] = (byte) (value >> 24);
        _buffer[_writeIndex++] = (byte) (value >> 16);
        _buffer[_writeIndex++] = (byte) (value >> 8);
        _buffer[_writeIndex++] = (byte) (value);
        return this;
    }

    public int readInt() {
        if (_writeIndex - _readIndex < 4) throw new IndexOutOfBoundsException();
        return (_buffer[_readIndex++] & (0xFF)) << 24
                | (_buffer[(_readIndex++)] & (0xFF)) << 16
                | (_buffer[(_readIndex++)] & (0xFF)) << 8
                | (_buffer[(_readIndex++)] & (0xFF));
    }

    public void clear() {
        _readIndex = _startIndex;
        _writeIndex = _startIndex;
    }

    private void compact() {
        int len = readRemaining();
        System.arraycopy(_buffer, _readIndex, _buffer, _startIndex, len);
        _readIndex = _startIndex;
        _writeIndex = _readIndex + len;
    }
}
